package com.gitee.linmt.util;

import org.slf4j.helpers.Util;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 消息格式化工具类.
 * copy from {@link org.slf4j.helpers.MessageFormatter}
 *
 * @author <a href="mailto:lin-mt@outlook.com">lin-mt</a>
 */
@SuppressWarnings("DuplicatedCode")
public final class MessageUtil {
    
    static final char DELIMITER_START = '{';
    
    static final String DELIMITER_STR = "{}";
    
    static final char ESCAPE_CHAR = '\\';
    
    /**
     * 格式化数组.
     *
     * @param messagePattern message 模板
     * @param params 参数
     * @return 格式化之后的 message
     */
    public static String arrayFormat(final String messagePattern, final Object[] params) {
        if (Objects.isNull(messagePattern) && Objects.isNull(params)) {
            return null;
        }
        if (Objects.isNull(params)) {
            return messagePattern;
        }
        int i = 0;
        int j;
        // use string builder for better multicore performance
        final StringBuilder sb = new StringBuilder(messagePattern.length() + 50);
        
        int l;
        for (l = 0; l < params.length; l++) {
            
            j = messagePattern.indexOf(MessageUtil.DELIMITER_STR, i);
            
            if (j == -1) {
                if (i == 0) {
                    return messagePattern;
                } else { // add the tail string which contains no variables and return
                    // the result.
                    sb.append(messagePattern, i, messagePattern.length());
                    return sb.toString();
                }
            } else {
                if (MessageUtil.isEscapedDelimiter(messagePattern, j)) {
                    if (!MessageUtil.isDoubleEscaped(messagePattern, j)) {
                        l--; // DELIMITER_START was escaped, thus should not be incremented
                        sb.append(messagePattern, i, j - 1);
                        sb.append(MessageUtil.DELIMITER_START);
                        i = j + 1;
                    } else {
                        // The escape character preceding the delimiter start is
                        // itself escaped: "abc x:\\{}"
                        // we have to consume one backward slash
                        sb.append(messagePattern, i, j - 1);
                        MessageUtil.deeplyAppendParameter(sb, params[l], new HashMap<>());
                        i = j + 2;
                    }
                } else {
                    // normal case
                    sb.append(messagePattern, i, j);
                    MessageUtil.deeplyAppendParameter(sb, params[l], new HashMap<>());
                    i = j + 2;
                }
            }
        }
        // append the characters following the last {} pair.
        sb.append(messagePattern, i, messagePattern.length());
        return sb.toString();
    }
    
    public static String format(final String messagePattern, final Object... params) {
        return MessageUtil.arrayFormat(messagePattern, params);
    }
    
    static boolean isEscapedDelimiter(final String messagePattern, final int delimiterStartIndex) {
        
        if (delimiterStartIndex == 0) {
            return false;
        }
        final char potentialEscape = messagePattern.charAt(delimiterStartIndex - 1);
        return potentialEscape == MessageUtil.ESCAPE_CHAR;
    }
    
    static boolean isDoubleEscaped(final String messagePattern, final int delimiterStartIndex) {
        return delimiterStartIndex >= 2 && messagePattern.charAt(delimiterStartIndex - 2) == MessageUtil.ESCAPE_CHAR;
    }
    
    private static void deeplyAppendParameter(final StringBuilder builder, final Object o,
            final Map<Object[], Object> seenMap) {
        if (o == null) {
            builder.append("null");
            return;
        }
        if (!o.getClass().isArray()) {
            MessageUtil.safeObjectAppend(builder, o);
        } else {
            // check for primitive array types because they
            // unfortunately cannot be cast to Object[]
            if (o instanceof boolean[]) {
                MessageUtil.booleanArrayAppend(builder, (boolean[]) o);
            } else if (o instanceof byte[]) {
                MessageUtil.byteArrayAppend(builder, (byte[]) o);
            } else if (o instanceof char[]) {
                MessageUtil.charArrayAppend(builder, (char[]) o);
            } else if (o instanceof short[]) {
                MessageUtil.shortArrayAppend(builder, (short[]) o);
            } else if (o instanceof int[]) {
                MessageUtil.intArrayAppend(builder, (int[]) o);
            } else if (o instanceof long[]) {
                MessageUtil.longArrayAppend(builder, (long[]) o);
            } else if (o instanceof float[]) {
                MessageUtil.floatArrayAppend(builder, (float[]) o);
            } else if (o instanceof double[]) {
                MessageUtil.doubleArrayAppend(builder, (double[]) o);
            } else {
                MessageUtil.objectArrayAppend(builder, (Object[]) o, seenMap);
            }
        }
    }
    
    private static void safeObjectAppend(final StringBuilder builder, final Object o) {
        try {
            final String oAsString = o.toString();
            builder.append(oAsString);
        } catch (final Throwable t) {
            Util.report("SLF4J: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t);
            builder.append("[FAILED toString()]");
        }
        
    }
    
    private static void objectArrayAppend(final StringBuilder builder, final Object[] a,
            final Map<Object[], Object> seenMap) {
        builder.append('[');
        if (!seenMap.containsKey(a)) {
            seenMap.put(a, null);
            final int len = a.length;
            for (int i = 0; i < len; i++) {
                MessageUtil.deeplyAppendParameter(builder, a[i], seenMap);
                if (i != len - 1) {
                    builder.append(", ");
                }
            }
            // allow repeats in siblings
            seenMap.remove(a);
        } else {
            builder.append("...");
        }
        builder.append(']');
    }
    
    private static void booleanArrayAppend(final StringBuilder builder, final boolean[] a) {
        builder.append('[');
        final int len = a.length;
        for (int i = 0; i < len; i++) {
            builder.append(a[i]);
            if (i != len - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
    }
    
    private static void byteArrayAppend(final StringBuilder builder, final byte[] a) {
        builder.append('[');
        final int len = a.length;
        for (int i = 0; i < len; i++) {
            builder.append(a[i]);
            if (i != len - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
    }
    
    private static void charArrayAppend(final StringBuilder builder, final char[] a) {
        builder.append('[');
        final int len = a.length;
        for (int i = 0; i < len; i++) {
            builder.append(a[i]);
            if (i != len - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
    }
    
    private static void shortArrayAppend(final StringBuilder builder, final short[] a) {
        builder.append('[');
        final int len = a.length;
        for (int i = 0; i < len; i++) {
            builder.append(a[i]);
            if (i != len - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
    }
    
    private static void intArrayAppend(final StringBuilder builder, final int[] a) {
        builder.append('[');
        final int len = a.length;
        for (int i = 0; i < len; i++) {
            builder.append(a[i]);
            if (i != len - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
    }
    
    private static void longArrayAppend(final StringBuilder builder, final long[] a) {
        builder.append('[');
        final int len = a.length;
        for (int i = 0; i < len; i++) {
            builder.append(a[i]);
            if (i != len - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
    }
    
    private static void floatArrayAppend(final StringBuilder builder, final float[] a) {
        builder.append('[');
        final int len = a.length;
        for (int i = 0; i < len; i++) {
            builder.append(a[i]);
            if (i != len - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
    }
    
    private static void doubleArrayAppend(final StringBuilder builder, final double[] a) {
        builder.append('[');
        final int len = a.length;
        for (int i = 0; i < len; i++) {
            builder.append(a[i]);
            if (i != len - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
    }
    
}
